﻿//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// WCF - System.ServiceModel.Internals\System\Runtime\TypeHelper.cs
//-----------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace CuteAnt.Reflection
{
  public static class TypeHelper
  {
    public static bool AreTypesCompatible(object source, Type destinationType)
    {
      if (source == null)
      {
        return !destinationType.IsValueType || IsNullableType(destinationType);
      }

      return AreTypesCompatible(source.GetType(), destinationType);
    }

    // return true if the sourceType is implicitly convertible to the destinationType
    public static bool AreTypesCompatible(Type sourceType, Type destinationType)
    {
      if (object.ReferenceEquals(sourceType, destinationType))
      {
        return true;
      }

      return IsImplicitNumericConversion(sourceType, destinationType) ||
          IsImplicitReferenceConversion(sourceType, destinationType) ||
          IsImplicitBoxingConversion(sourceType, destinationType) ||
          IsImplicitNullableConversion(sourceType, destinationType);
    }

    // simpler, more performant version of AreTypesCompatible when
    // we know both sides are reference types
    public static bool AreReferenceTypesCompatible(Type sourceType, Type destinationType)
    {
      //Fx.Assert(!sourceType.IsValueType && !destinationType.IsValueType, "AreReferenceTypesCompatible can only be used for reference types");
      if(sourceType.IsValueType || destinationType.IsValueType)
      {
        throw new ArgumentException("AreReferenceTypesCompatible can only be used for reference types");
      }
      if (object.ReferenceEquals(sourceType, destinationType))
      {
        return true;
      }

      return IsImplicitReferenceConversion(sourceType, destinationType);
    }

    // variation to OfType<T> that uses AreTypesCompatible instead of Type equality
    public static IEnumerable<Type> GetCompatibleTypes(IEnumerable<Type> enumerable, Type targetType)
    {
      foreach (Type sourceType in enumerable)
      {
        if (TypeHelper.AreTypesCompatible(sourceType, targetType))
        {
          yield return sourceType;
        }
      }
    }

    public static bool ContainsCompatibleType(IEnumerable<Type> enumerable, Type targetType)
    {
      foreach (Type sourceType in enumerable)
      {
        if (TypeHelper.AreTypesCompatible(sourceType, targetType))
        {
          return true;
        }
      }

      return false;
    }

    // handles not only the simple cast, but also value type widening, etc.
    public static T Convert<T>(object source)
    {
      // first check the common cases
      if (source is T) { return (T)source; }

      #region ## 苦竹 修改 ##
      //if (source == null)
      //{
      //  if (typeof(T).IsValueType && !IsNullableType(typeof(T)))
      //  {
      //    throw Fx.Exception.AsError(new InvalidCastException(InternalSR.CannotConvertObject(source, typeof(T))));
      //  }

      //  return default(T);
      //}

      //T result;
      //if (TryNumericConversion<T>(source, out result))
      //{
      //  return result;
      //}

      //throw new InvalidCastException($"Cannot convert object {source} to {conversionType}");

      var conversionType = typeof(T);
      if (source == null)
      {
        if (conversionType.IsValueType && !IsNullableType(conversionType))
        {
          throw new InvalidCastException($"Cannot convert object {source} to {conversionType}");
        }

        return default(T);
      }

      if (TryNumericConversion<T>(source, out T result))
      {
        return result;
      }

      return TypeUtils.ChangeType<T>(source);

      #endregion
    }

    // get all of the types that this Type implements (based classes, interfaces, etc)
    public static IEnumerable<Type> GetImplementedTypes(Type type)
    {
      Dictionary<Type, object> typesEncountered = new Dictionary<Type, object>();

      GetImplementedTypesHelper(type, typesEncountered);
      return typesEncountered.Keys;
    }

    //[SuppressMessage(FxCop.Category.Usage, "CA2301:EmbeddableTypesInContainersRule", MessageId = "typesEncountered", Justification = "No need to support type equivalence here.")]
    private static void GetImplementedTypesHelper(Type type, Dictionary<Type, object> typesEncountered)
    {
      if (typesEncountered.ContainsKey(type))
      {
        return;
      }

      typesEncountered.Add(type, type);

      Type[] interfaces = type.GetInterfaces();
      for (int i = 0; i < interfaces.Length; ++i)
      {
        GetImplementedTypesHelper(interfaces[i], typesEncountered);
      }

      Type baseType = type.BaseType;
      while ((baseType != null) && (baseType != TypeConstants.ObjectType))
      {
        GetImplementedTypesHelper(baseType, typesEncountered);
        baseType = baseType.BaseType;
      }
    }

    //[SuppressMessage(FxCop.Category.Maintainability, FxCop.Rule.AvoidExcessiveComplexity,
    //    Justification = "Need to check all possible numeric conversions")]
    private static bool IsImplicitNumericConversion(Type source, Type destination)
    {
      TypeCode sourceTypeCode = Type.GetTypeCode(source);
      TypeCode destinationTypeCode = Type.GetTypeCode(destination);

      switch (sourceTypeCode)
      {
        case TypeCode.SByte:
          switch (destinationTypeCode)
          {
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.Single:
            case TypeCode.Double:
            case TypeCode.Decimal:
              return true;
          }
          return false;

        case TypeCode.Byte:
          switch (destinationTypeCode)
          {
            case TypeCode.Int16:
            case TypeCode.UInt16:
            case TypeCode.Int32:
            case TypeCode.UInt32:
            case TypeCode.Int64:
            case TypeCode.UInt64:
            case TypeCode.Single:
            case TypeCode.Double:
            case TypeCode.Decimal:
              return true;
          }
          return false;

        case TypeCode.Int16:
          switch (destinationTypeCode)
          {
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.Single:
            case TypeCode.Double:
            case TypeCode.Decimal:
              return true;
          }
          return false;

        case TypeCode.UInt16:
          switch (destinationTypeCode)
          {
            case TypeCode.Int32:
            case TypeCode.UInt32:
            case TypeCode.Int64:
            case TypeCode.UInt64:
            case TypeCode.Single:
            case TypeCode.Double:
            case TypeCode.Decimal:
              return true;
          }
          return false;

        case TypeCode.Int32:
          switch (destinationTypeCode)
          {
            case TypeCode.Int64:
            case TypeCode.Single:
            case TypeCode.Double:
            case TypeCode.Decimal:
              return true;
          }
          return false;

        case TypeCode.UInt32:
          switch (destinationTypeCode)
          {
            case TypeCode.UInt32:
            case TypeCode.Int64:
            case TypeCode.UInt64:
            case TypeCode.Single:
            case TypeCode.Double:
            case TypeCode.Decimal:
              return true;
          }
          return false;

        case TypeCode.Int64:
        case TypeCode.UInt64:
          switch (destinationTypeCode)
          {
            case TypeCode.Single:
            case TypeCode.Double:
            case TypeCode.Decimal:
              return true;
          }
          return false;

        case TypeCode.Char:
          switch (destinationTypeCode)
          {
            case TypeCode.UInt16:
            case TypeCode.Int32:
            case TypeCode.UInt32:
            case TypeCode.Int64:
            case TypeCode.UInt64:
            case TypeCode.Single:
            case TypeCode.Double:
            case TypeCode.Decimal:
              return true;
          }
          return false;

        case TypeCode.Single:
          return (destinationTypeCode == TypeCode.Double);
      }

      return false;
    }

    private static bool IsImplicitReferenceConversion(Type sourceType, Type destinationType)
    {
      return destinationType.IsAssignableFrom(sourceType);
    }

    private static bool IsImplicitBoxingConversion(Type sourceType, Type destinationType)
    {
      if (sourceType.IsValueType && (destinationType == TypeConstants.ObjectType || destinationType == TypeConstants.ValueType))
      {
        return true;
      }
      if (sourceType.IsEnum && destinationType == typeof(Enum))
      {
        return true;
      }
      return false;
    }

    private static bool IsImplicitNullableConversion(Type sourceType, Type destinationType)
    {
      if (!IsNullableType(destinationType))
      {
        return false;
      }

      destinationType = destinationType.GetGenericArguments()[0];
      if (IsNullableType(sourceType))
      {
        sourceType = sourceType.GetGenericArguments()[0];
      }
      return AreTypesCompatible(sourceType, destinationType);
    }

    private static bool IsNullableType(Type type)
    {
      return type.IsGenericType && type.GetGenericTypeDefinition() == TypeConstants.NullableType;
    }

    private static bool TryNumericConversion<T>(object source, out T result)
    {
      //Fx.Assert(source != null, "caller must verify");
      TypeCode sourceTypeCode = Type.GetTypeCode(source.GetType());
      TypeCode destinationTypeCode = Type.GetTypeCode(typeof(T));

      switch (sourceTypeCode)
      {
        case TypeCode.SByte:
          {
            SByte sbyteSource = (SByte)source;
            switch (destinationTypeCode)
            {
              case TypeCode.Int16:
                result = (T)(object)(Int16)sbyteSource;
                return true;

              case TypeCode.Int32:
                result = (T)(object)(Int32)sbyteSource;
                return true;

              case TypeCode.Int64:
                result = (T)(object)(Int64)sbyteSource;
                return true;

              case TypeCode.Single:
                result = (T)(object)(Single)sbyteSource;
                return true;

              case TypeCode.Double:
                result = (T)(object)(Double)sbyteSource;
                return true;

              case TypeCode.Decimal:
                result = (T)(object)(Decimal)sbyteSource;
                return true;
            }
            break;
          }
        case TypeCode.Byte:
          {
            Byte byteSource = (Byte)source;
            switch (destinationTypeCode)
            {
              case TypeCode.Int16:
                result = (T)(object)(Int16)byteSource;
                return true;

              case TypeCode.UInt16:
                result = (T)(object)(UInt16)byteSource;
                return true;

              case TypeCode.Int32:
                result = (T)(object)(Int32)byteSource;
                return true;

              case TypeCode.UInt32:
                result = (T)(object)(UInt32)byteSource;
                return true;

              case TypeCode.Int64:
                result = (T)(object)(Int64)byteSource;
                return true;

              case TypeCode.UInt64:
                result = (T)(object)(UInt64)byteSource;
                return true;

              case TypeCode.Single:
                result = (T)(object)(Single)byteSource;
                return true;

              case TypeCode.Double:
                result = (T)(object)(Double)byteSource;
                return true;

              case TypeCode.Decimal:
                result = (T)(object)(Decimal)byteSource;
                return true;
            }
            break;
          }
        case TypeCode.Int16:
          {
            Int16 int16Source = (Int16)source;
            switch (destinationTypeCode)
            {
              case TypeCode.Int32:
                result = (T)(object)(Int32)int16Source;
                return true;

              case TypeCode.Int64:
                result = (T)(object)(Int64)int16Source;
                return true;

              case TypeCode.Single:
                result = (T)(object)(Single)int16Source;
                return true;

              case TypeCode.Double:
                result = (T)(object)(Double)int16Source;
                return true;

              case TypeCode.Decimal:
                result = (T)(object)(Decimal)int16Source;
                return true;
            }
            break;
          }
        case TypeCode.UInt16:
          {
            UInt16 uint16Source = (UInt16)source;
            switch (destinationTypeCode)
            {
              case TypeCode.Int32:
                result = (T)(object)(Int32)uint16Source;
                return true;

              case TypeCode.UInt32:
                result = (T)(object)(UInt32)uint16Source;
                return true;

              case TypeCode.Int64:
                result = (T)(object)(Int64)uint16Source;
                return true;

              case TypeCode.UInt64:
                result = (T)(object)(UInt64)uint16Source;
                return true;

              case TypeCode.Single:
                result = (T)(object)(Single)uint16Source;
                return true;

              case TypeCode.Double:
                result = (T)(object)(Double)uint16Source;
                return true;

              case TypeCode.Decimal:
                result = (T)(object)(Decimal)uint16Source;
                return true;
            }
            break;
          }
        case TypeCode.Int32:
          {
            Int32 int32Source = (Int32)source;
            switch (destinationTypeCode)
            {
              case TypeCode.Int64:
                result = (T)(object)(Int64)int32Source;
                return true;

              case TypeCode.Single:
                result = (T)(object)(Single)int32Source;
                return true;

              case TypeCode.Double:
                result = (T)(object)(Double)int32Source;
                return true;

              case TypeCode.Decimal:
                result = (T)(object)(Decimal)int32Source;
                return true;
            }
            break;
          }
        case TypeCode.UInt32:
          {
            UInt32 uint32Source = (UInt32)source;
            switch (destinationTypeCode)
            {
              case TypeCode.UInt32:
                result = (T)(object)(UInt32)uint32Source;
                return true;

              case TypeCode.Int64:
                result = (T)(object)(Int64)uint32Source;
                return true;

              case TypeCode.UInt64:
                result = (T)(object)(UInt64)uint32Source;
                return true;

              case TypeCode.Single:
                result = (T)(object)(Single)uint32Source;
                return true;

              case TypeCode.Double:
                result = (T)(object)(Double)uint32Source;
                return true;

              case TypeCode.Decimal:
                result = (T)(object)(Decimal)uint32Source;
                return true;
            }
            break;
          }
        case TypeCode.Int64:
          {
            Int64 int64Source = (Int64)source;
            switch (destinationTypeCode)
            {
              case TypeCode.Single:
                result = (T)(object)(Single)int64Source;
                return true;

              case TypeCode.Double:
                result = (T)(object)(Double)int64Source;
                return true;

              case TypeCode.Decimal:
                result = (T)(object)(Decimal)int64Source;
                return true;
            }
            break;
          }
        case TypeCode.UInt64:
          {
            UInt64 uint64Source = (UInt64)source;
            switch (destinationTypeCode)
            {
              case TypeCode.Single:
                result = (T)(object)(Single)uint64Source;
                return true;

              case TypeCode.Double:
                result = (T)(object)(Double)uint64Source;
                return true;

              case TypeCode.Decimal:
                result = (T)(object)(Decimal)uint64Source;
                return true;
            }
            break;
          }
        case TypeCode.Char:
          {
            Char charSource = (Char)source;
            switch (destinationTypeCode)
            {
              case TypeCode.UInt16:
                result = (T)(object)(UInt16)charSource;
                return true;

              case TypeCode.Int32:
                result = (T)(object)(Int32)charSource;
                return true;

              case TypeCode.UInt32:
                result = (T)(object)(UInt32)charSource;
                return true;

              case TypeCode.Int64:
                result = (T)(object)(Int64)charSource;
                return true;

              case TypeCode.UInt64:
                result = (T)(object)(UInt64)charSource;
                return true;

              case TypeCode.Single:
                result = (T)(object)(Single)charSource;
                return true;

              case TypeCode.Double:
                result = (T)(object)(Double)charSource;
                return true;

              case TypeCode.Decimal:
                result = (T)(object)(Decimal)charSource;
                return true;
            }
            break;
          }
        case TypeCode.Single:
          {
            if (destinationTypeCode == TypeCode.Double)
            {
              result = (T)(object)(Double)(Single)source;
              return true;
            }
            break;
          }
      }

      result = default(T);
      return false;
    }

    public static object GetDefaultValueForType(Type type)
    {
      if (!type.IsValueType)
      {
        return null;
      }

      if (type.IsEnum)
      {
        Array enumValues = Enum.GetValues(type);
        if (enumValues.Length > 0)
        {
          return enumValues.GetValue(0);
        }
      }

      return ActivatorUtils.FastCreateInstance(type);
    }

    public static bool IsNullableValueType(Type type)
    {
      return type.IsValueType && IsNullableType(type);
    }

    public static bool IsNonNullableValueType(Type type)
    {
      if (!type.IsValueType)
      {
        return false;
      }

      if (type.IsGenericType)
      {
        return false;
      }

      return type != TypeConstants.StringType;
    }

    public static bool ShouldFilterProperty(PropertyDescriptor property, Attribute[] attributes)
    {
      if (attributes == null || attributes.Length == 0)
      {
        return false;
      }

      for (int i = 0; i < attributes.Length; i++)
      {
        Attribute filterAttribute = attributes[i];
        Attribute propertyAttribute = property.Attributes[filterAttribute.GetType()];
        if (propertyAttribute == null)
        {
          if (!filterAttribute.IsDefaultAttribute())
          {
            return true;
          }
        }
        else
        {
          if (!filterAttribute.Match(propertyAttribute))
          {
            return true;
          }
        }
      }
      return false;
    }
  }
}